Post

Replies

Boosts

Views

Activity

Reply to How to change text in SwiftUI conditional button to systemImage?
You're calling a Button initializer that expects a String as its first parameter. Instead, you need to use a different initializer to provide the button's content. To actually change the symbol being used, you can use the .symbolVariant modifier to switch between a variant of .fill and a variant of .none.         .toolbar {             ToolbarItem(placement: .navigationBarTrailing) {                 Button {                     if favorites.contains(item) {                         favorites.remove(item)                     } else {                         favorites.add(item)                     }                 } label: {                 Image(systemImage: "heart")                 .symbolVariant(favorites.contains(item) ? .fill : .none)                 }             }         }
Nov ’22
Reply to UI with FetchRequest dosn’t refresh
@FetchRequest always uses the managed object context from the environment; in general, you should always do the same.When I've used SwiftUI fetch requests I've always used the property wrapper syntax, and it's worked happily enough. For example, I have a simple to-do list view:struct TodoList: View { @FetchRequest<TodoItem> private var items: FetchedResults<TodoItem> init(request: NSFetchRequest<TodoItem>) { self._items = FetchRequest(fetchRequest: request) } var body: some View { List { ForEach(items, id: \.objectID) { item in ItemRow(item: item) } } } }The ItemRow then uses an @ObservedObject to see the item, and reads the managed object context from the environment:struct ItemRow: View { @ObservedObject var item: TodoItem @Environment(\.managedObjectContext) private var managedObjectContext }So far, this approach has worked for me, including doing things like editing items directly, adding and removing them. I'd recommend trying to diagnose your issue by taking a step back and attempting to build the UI without too much extra code around the model; see how far @FetchRequest and @ObservedObject alone can get you before writing your own code to load and save individual items. That way you'll have a better chance of locating the underlying issue.
Feb ’20
Reply to ANOTHER EXAMPLE OF SWIFTUI GARBAGE!
Tell us how you really feel 😉As it stands, neither Double.round, NumberFormatter, nor NSFormat have anything to do with SwiftUI. The first is part of the Swift standard library, and the latter two are part of the Foundation framework that's been around for about 30 years now.As well as FedyaninT's suggestion of the specifier: argument within LocalizedStringKey's string interpolation implementation, NumberFormatter would also work when configured appropriately. Note that Double.round() is a mathematical function; it's used to round floating-point values to the closest integer value, and thus doesn't have anything to do with what you need.NumberFormatter has a vast array of options, and will let you format integer and real values in almost every way defined as part of the Unicode and CLDR standard (the only exception I'm aware of is 'shortened' formats, e.g. "10 M" for "10000000"). To round a value to two decimal places you need to set a numberStyle of .decimal and set both .minimumFractionDigits and .maximumFractionDigits to '2'.From memory, this is what you'd need:static let formatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.minimumFractionDigits = 2 formatter.maximumFractionDigits = 2 return formatter }() ... let value = 123.456 Text("Your value is: \(value, formatter: formatter)") // "Your value is: 123.45"Alternatively, if you're trimming to two decimal places in order to print a value in some currency, I would advise using NumberFormatter's .currency style instead:static let formatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .currency return formatter }() ... let value = 123.456 Text("Your value is: \(value, formatter: formatter)") // "Your value is: $123.45" (or 123.45 €, if you're using the FR_fr locale)
Feb ’20
Reply to Can't use protocols with SwiftUI models?
One approach that's used throughout SwiftUI is the concept of a proxy type. For instance, scroll views internally use a proxy type to read and write things like the content offset. It may be possible for your generic Marker type to vend optional proxy objects for different features, and only the types that support a particular setting provide that type of proxy:struct SizeProxy { @Binding size: Double } ... Text(marker.name) if marker.sizeProxy != nil { Slider(marker.sizeProxy!.$size, ...) }
Feb ’20
Reply to How to lock screen orientation for a specific view?
Oooh, I know this one! There's a discussion here: SwiftUI: Force orientation on a per screen basis.I also implemented this as part of my open-source toolkit: https://github.com/AlanQuatermain/AQUI/blob/master/Sources/AQUI/OrientationLock.swiftThe version on Github allows views to specify their individual constraints, and will collapse them all into a single allowable state. If one view says 'all but upside down' and another says 'portrait only' then the effective value will be 'portrait only'.
Feb ’20
Reply to Can't use protocols with SwiftUI models?
Making your Marker type vend its view works:protocol Marker : ObservableObject { associatedtype Body: View var name: String { get set } var body: Body { get } } protocol MarkerWithSize: Marker { var size: Float { get set } } class UnsizedMarker: Marker { init() {} @Published var name: String = "UnsizedMarker" var body: some View { VStack { Text("Marker name: \(name)") } } } class BasicMarker : MarkerWithSize { init() {} @Published var name: String = "test" @Published var size: Float = 1.0 var body: some View { VStack { Text("Marker name: \(name)") Text("Size: \(size)") Slider(value: Binding(get: {self.size}, set: {self.size=$0}), in: 1...50) } } } struct ContentView<MT: Marker>: View { @ObservedObject var marker: MT var body: some View { marker.body } } struct ContentView_Previews: PreviewProvider { static var previews: some View { VStack { ContentView(marker: UnsizedMarker()) Divider() ContentView(marker: BasicMarker()) } } }I'm inclined to think that this situation isn't quite a fit for the protocol approach, though. Since you're relying on ObservableObject at the root of the protocol tree, you're already limited to class types, which means you can just use classes all the way and use inheritance to do what you need. Also, your sample at least is declaring two inheriting protocols, which further suggests that classes are the way to go: protocols are usually used to manage several disparate sets of requirements, such as Equatable and CustomStringConvertible.Here's a class-based approach that works:class Marker: ObservableObject { @Published var name: String = "Marker" } class MarkerWithSize: Marker { @Published var size: Float = 1.0 override init() { super.init() self.name = "SizedMarker" } } struct ContentView: View { @ObservedObject var marker: Marker var body: some View { VStack { Text("Marker name: \(marker.name)") if marker is MarkerWithSize { MarkerWithSizeSection(marker: marker as! MarkerWithSize) } } } } struct MarkerWithSizeSection: View { @ObservedObject var marker: MarkerWithSize var body: some View { VStack { Text("Size: \(marker.size)") Slider(value: $marker.size, in: 1...50) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { VStack { ContentView(marker: Marker()) Divider() ContentView(marker: MarkerWithSize()) } } }
Feb ’20
Reply to Missing/Broken features in SwiftUI [List]
You might be able to override the gestures in a navigation link by assigning your own gestures to it with a GestureMask of .gesture. That would disable the gestures registered on the NavigationLink itself. Of course, the gesture it's receiving may be part of the List implementation, so it may or may not work. That would be my first try, anyway. Then you'd define the link using one of the other initializers: init(destination:isActive:label:) or init(destination:tag:selection:label:). You'd then be able to use your gestures to set the appropriate values in some state variables, and the links bound to those would fire.
Jan ’20
Reply to SwiftUI: DoubleColumn: Pre-select view from Master?
Depending on the number of items in your list (in your example there aren't many) then you can use a state variable to keep track of which item is selected, and tell SwiftUI to display a particular detail view by setting that variable yourself.For example, you have General, Help, Contact, and About items. That would work as an enum, like so:enum SettingsPane: Equatable, Identifiable { case none case general case help case contact case about var id: SettingsPane { self } }With this in place, you create a state variable:@State var whichPane: SettingsPane = .noneNow you change the declaration of each of your navigation links. There are three primary initializers, and you're using the first: init(destination:label:). There's also init(destination:isActive:), which takes a binding to a Bool value that will be used to show or hide it. Lastly there's init(destination:tag:selection:label:), which goes even further: it assigns a particular value to this link, and then binds to a variable of the same type. Whenever that variable's value is set to the assigned tag, the link fires and pushes the destination view.Additionally, the links in all cases will set the appropriate value in any bindings when they're tapped on manually, so you'll be able to keep track of them that way, too.So, using the enum and property defined above, you could have:NavigationLink(destination: SettingsGeneralView(), tag: .general, selection: $whichPane) { ... } NavigationLink(destination: SettingsHelpView(), tag: .help, selection: $whichPane) { ... } NavigationLink(destination: SettingsContactView(), tag: .contact, selection: $whichPane) { ... } NavigationLink(destination: SettingsAboutView(), tag: .about, selection: $whichPane) { ... }It's then up to you record and reassign the selection value somehow. For instance, you might use .onAppear:List { ... } .onAppear { self.whichPane = self.globalSettings.currentSettingsPane }Or you might bind directly to the value in your preferences:NavigationLink(destination: ..., tag: ..., selection: $globalSettings.currentSettingsPane) { ... }Another option would be to use the UserDefaults to both vend and record the value. A property wrapper that reads and writes the UserDefaults and vends a working Binding would serve. Something like this:@propertyWrapper struct UserDefault<Value> { let key: String let defaultValue: Value init(_ key: String, defaultValue: Value) { self.key = key self.defaultValue = defaultValue } var wrappedValue: Value { get { Self.getWrapped(forKey: key, defaultValue: defaultValue) } mutating set { Self.setWrapped(value: newValue, forKey: key) } } var projectedValue: Binding<Value> { Binding(get: { Self.getWrapped(forKey: self.key, defaultValue: self.defaultValue) }, set: { Self.setWrapped(value: $0, forKey: self.key) }) } static private func getWrapped(forKey key: String, defaultValue: Value) -> Value { UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue } static private func setWrapped(value: Value, forKey key: String) { UserDefaults.standard.set(value, forKey: key) } }With this, you need only change the declaration of your state variable:@UserDefault("my.app.settings.current-pane", defaultValue: .none) var whichPane: SettingsPaneThat should do what you need, and you've a few different options for ways to store & retrieve the value as necessary.
Jan ’20
Reply to Using an existing Core Data database with SwiftUI Previews
I use the following preview-specific class within Preview Content to load my model and create a fresh copy of the database inside the Caches directory, importing some sample data:import Foundation import CoreData import UIKit class PreviewManagedObjectContext { static let shared = try! PreviewManagedObjectContext() let storeCoordinator: NSPersistentStoreCoordinator let objectModel: NSManagedObjectModel let viewContext: NSManagedObjectContext init() throws { let storeURL = try FileManager.default.url( for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("Previews.db") try? FileManager.default.removeItem(at: storeURL) objectModel = NSManagedObjectModel.mergedModel(from: nil)! storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: objectModel) try storeCoordinator.addPersistentStore( ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [:]) viewContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) viewContext.persistentStoreCoordinator = storeCoordinator guard let url = Bundle.main.url(forResource: "todo-items", withExtension: "json") else { return } viewContext.performAndWait { try? importSampleData(from: url, to: viewContext) } } func backgroundContext() -> NSManagedObjectContext { let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) context.persistentStoreCoordinator = storeCoordinator return context } var sampleList: TodoItemList { let request: NSFetchRequest<TodoItemList> = TodoItemList.fetchRequest() request.fetchLimit = 1 request.predicate = NSPredicate(format: "name ==[c] %@", "SwiftUI Book") let items = try? viewContext.fetch(request) return items!.first! } var sampleItem: ToDo { let request: NSFetchRequest<ToDo> = ToDo.fetchRequest() request.fetchLimit = 1 request.predicate = NSPredicate(format: "title ==[c] %@", "Complete SwiftUI book sample") let items = try? viewContext.fetch(request) return items!.first! } }I then use it in my previews like so:struct TodoItemRow_Previews: PreviewProvider { static var previews: some View { let context = PreviewManagedObjectContext.shared.viewContext let object = PreviewManagedObjectContext.shared.sampleItem return EditModePreviewWrapper(editing: true) { ForEach(ColorScheme.allCases, id: \.self) { scheme in List { TodoItemRow(item: object) } .colorScheme(scheme) .previewDisplayName(String(describing: scheme)) } } .environment(\.managedObjectContext, context) .previewLayout(.fixed(width: 400, height: 120)) } }
Jan ’20
Reply to How to make a drop target in SwiftUI on iOS
I'd suspected that might be the case. There's likely a solution for dragging within an app that just uses gestures and anchors to change state when one item is dragged over another. For dragging between apps, I suspect the only way to implement it right now is to wrap things in a UIViewRepresentable to get access to the UIView/UIResponder methods for drag & drop. You may be able to implement this as an overlay to a SwiftUI view; I'll see if I can't figure out a graceful way of doing this.
Jan ’20